import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Client } from "src/modules/client/client.entity";
import { ClientService } from "src/modules/client/client.service";
import { CompanyService } from "src/modules/company/company.service";
import { DeliveryItem } from "src/modules/delivery-item/delivery-item.entity";
import { Delivery } from "src/modules/delivery/delivery.entity";
import { Order } from "src/modules/order/order.entity";
import { StatisticService } from "src/modules/statistics/statistic.service";
import { UserService } from "src/modules/user/user.service";
import { CustomizeStatus } from "src/utils/status";
import { itemCompanyCheck } from "src/utils/util";
import { EntityManager, getManager, Repository } from "typeorm";
import { CreateDeliveryDto, ItemDto } from "./dto/create-delivery.dto";
import { UpdateDeliveryItemDto } from "./dto/update-delivery-item.dto";

@Injectable()
export class DeliverOrderService {
  constructor(
    @InjectRepository(Order)
    private readonly orderRepository: Repository<Order>,
    @InjectRepository(Delivery)
    private readonly deliveryRepository: Repository<Delivery>,
    @InjectRepository(DeliveryItem)
    private readonly deliveryItemRepository: Repository<DeliveryItem>,
    private readonly companyService: CompanyService,
    private readonly usersService: UserService,
    private readonly clientService: ClientService,
    private readonly statisticService: StatisticService
  ) {}

  // 送货, 创建送货单
  async deliver(
    createDeliveryDto: CreateDeliveryDto,
    companyId: string,
    userId: string
  ): Promise<Delivery> {
    const delivery = new Delivery();
    const [user, company, client] = await Promise.all([
      this.usersService.find(userId),
      this.companyService.find(companyId),
      this.clientService.find(createDeliveryDto.clientId),
    ]);
    itemCompanyCheck(company, client);
    for (const key of ["address", "contact"]) {
      delivery[key] = createDeliveryDto[key];
    }
    delivery.deliveryDate = new Date(createDeliveryDto.deliveryDate);
    delivery.no = (
      new Date().toJSON().split("T")[0].split("-").join("") +
      (10000 + (await this.statisticService.todayDelivery(company)))
        .toString()
        .substring(1)
    ).substring(2);
    Object.assign(delivery, {
      company,
      user,
      client,
    });
    const ans = await Promise.all(
      createDeliveryDto.items.map(async (item) => {
        const order = await this.orderRepository.findOneOrFail({
          where: { id: item.orderId },
          relations: ["client"],
        });
        // 此处已经验证了订单客户一致性, 不必再验证公司一致性了
        this.orderDeliveryCheck(order, client);
        const { deliveryItem } = this.deliverItem(order, item, delivery);
        return [deliveryItem, order];
      })
    );
    const res = await getManager()
      .transaction(async (entityManage: EntityManager) => {
        const deliverySaved = await entityManage.save(Delivery, delivery);
        await Promise.all(
          ans.map(([deliveryItem, order]) =>
            Promise.all([
              entityManage.save(DeliveryItem, deliveryItem),
              entityManage.save(Order, order),
            ])
          )
        );
        return deliverySaved;
      })
      .then(async (res) => {
        await this.statisticService.appendDelivery(company);
        return this.deliveryRepository.findOneOrFail(res.id, {
          relations: ["deliveryItems", "client"],
        });
      });

    return res;
  }

  // 取消送货, 删除整张送货单
  async unDeliver(delivery: string | Delivery): Promise<Delivery> {
    if (!(delivery instanceof Delivery)) {
      delivery = await this.deliveryRepository.findOneOrFail(delivery);
    }
    const items = await this.deliveryItemRepository.find({
      where: { delivery },
      relations: ["order"],
    });
    // 修改送货单中所有对应Order的剩余数量
    const orders = items.map((item) => {
      item.order.newLeft = item.order.left + item.quantity;
      return item.order;
    });
    await getManager().transaction(async (entityManage: EntityManager) => {
      await Promise.all(
        items.map((item) => entityManage.delete(DeliveryItem, item))
      );
      await Promise.all(orders.map((item) => entityManage.save(Order, item)));
    });
    await this.deliveryRepository.remove(delivery);
    return delivery;
  }

  /**
   * 送货单增加单项
   * @param id 送货单ID
   * @param itemDto 送货单项结构
   */
  async deliveryAppendItem(id: string, itemDto: ItemDto): Promise<Delivery> {
    const delivery = await this.deliveryRepository.findOneOrFail({
      where: { id },
      relations: ["client"],
    });
    const order = await this.orderRepository.findOneOrFail({
      where: { id: itemDto.orderId },
      relations: ["client"],
    });
    const { order: updateOrder, deliveryItem } = this.deliverItem(
      order,
      itemDto,
      delivery
    );
    this.orderDeliveryCheck(order, delivery);
    return await getManager()
      .transaction(async (entityManage: EntityManager) => {
        await Promise.all([
          entityManage.save(DeliveryItem, deliveryItem),
          entityManage.save(Order, updateOrder),
        ]);
      })
      .then(async () => {
        return this.deliveryRepository.findOneOrFail(delivery.id, {
          relations: ["deliveryItems", "client"],
        });
      });
  }

  /**
   * 送货单删除其中一项
   * @param id 送货单项ID
   */
  async deliveryDeleteItem(item: string | DeliveryItem) {
    const deliveryItem =
      item instanceof DeliveryItem
        ? item
        : await this.deliveryItemRepository.findOneOrFail(item);
    const order = deliveryItem.order;
    const delivery = await this.deliveryRepository.findOneOrFail({
      where: { id: deliveryItem.delivery.id },
      relations: ["deliveryItems"],
    });
    order.newLeft = order.left + deliveryItem.quantity;
    return await getManager()
      .transaction(async (entityManage: EntityManager) => {
        if (delivery.deliveryItems.length === 1) {
          await Promise.all([
            entityManage.delete(DeliveryItem, deliveryItem),
            entityManage.save(Order, order),
            entityManage.delete(Delivery, delivery),
          ]);
        } else {
          await Promise.all([
            entityManage.delete(DeliveryItem, deliveryItem),
            entityManage.save(Order, order),
            entityManage.save(Delivery, delivery),
          ]);
        }

        return deliveryItem;
      })
      .then(async () => {
        return this.deliveryRepository.findOneOrFail(delivery.id, {
          relations: ["deliveryItems", "client"],
        });
      });
  }

  // 送货单修改单项(送货数量)
  async deliveryUpdateItem(
    item: string | DeliveryItem,
    updateDeliveryDto: UpdateDeliveryItemDto
  ): Promise<DeliveryItem> {
    const { quantity } = updateDeliveryDto;
    const deliveryItem =
      item instanceof DeliveryItem
        ? item
        : await this.deliveryItemRepository.findOneOrFail(item);
    const order = await this.orderRepository.findOneOrFail(
      deliveryItem.order.id
    );
    const difference = quantity - deliveryItem.quantity;
    deliveryItem.quantity = quantity;
    order.newLeft = order.left - difference;
    const delivery = deliveryItem.delivery;
    return await getManager().transaction(
      async (entityManage: EntityManager) => {
        await Promise.all([
          entityManage.save(DeliveryItem, deliveryItem),
          entityManage.save(Order, order),
          entityManage.save(Delivery, delivery),
        ]);
        return deliveryItem;
      }
    );
  }

  /**
   * 将 订单 - 送货单项 - 送货单 联立起来
   * @param order 送货单项对应的订单
   * @param item 送货单项信息
   * @param delivery 送货单项所在的送货单
   */
  private deliverItem(order: Order, item: ItemDto, delivery: Delivery) {
    order.newLeft = order.left - item.quantity;
    const deliveryItem = new DeliveryItem();
    deliveryItem.order = order;
    deliveryItem.delivery = delivery;
    deliveryItem.quantity = item.quantity;
    return { deliveryItem, order };
  }

  private orderDeliveryCheck(...args: (Order | Client | Delivery)[]) {
    const ids = args.map((item) =>
      item instanceof Client ? item.id : item.client.id
    );
    if (new Set(ids).size > 1) {
      throw new ClientConflict(...args);
    }
  }
}

class ClientConflict extends HttpException {
  constructor(...target: (Order | Client | Delivery)[]) {
    super(
      {
        message: "订单与送货单的客户冲突",
        statusCode: CustomizeStatus.TARGET_CONFLICT,
        orderId: target[0].id,
        target,
      },
      HttpStatus.FORBIDDEN
    );
  }
}
